#!/usr/bin/env python
# 
#  salim - the standard implementation for the salim personal financial manager API
#  

import sys
import os
import logging
import getopt
import datetime
import time

sys.path.append('../src')

from easycsv import StormORM

from salim.ofx import OFXFileParser
from salim.model import *
from salim import facade
from salim import model


APP_NAME = 'salim'
SALIM_HOME = os.path.join(os.environ['HOME'], '.salim')

LOG_LEVEL = {
    'critical': logging.DEBUG,
    'error':    logging.ERROR,
    'warning':  logging.WARNING,
    'info':     logging.INFO,
    'debug':    logging.DEBUG,
    'notset':   logging.NOTSET
}

class GetOptions(object):
    def __init__(self):
        self.long_options = ['help', 'verbosity-level=', 'export-memos', 'import-csv',
        'import-ofx', 'server', 'date=', 'list-balance', 'account=', 'report']
        self.options = 'hv:'
        self.verbosity_level = logging.INFO
        self.actions = []
        self.action = None
        year, month, a, b, c, d, e, f, g = time.localtime()
        self.date = (year, month)

    def parse_options(self, argv):
        opts, args = getopt.getopt(argv, self.options, self.long_options)
        for opt, val in opts:
            if opt in ['-h', '--help']:
                self.usage_extended()
            if opt in ['--verbosity-level', '-v']:
                self.verbosity_level = LOG_LEVEL[val]
            if opt in ['--export-memos', '--import-csv', '--import-ofx', '--list-balance', '--report']:
                self.action = opt[2:]
            if opt == '--date':
                year  = int(val[:4])
                month = int(val[4:])
                self.date = (year, month)
            if opt == '--account':
                self.account = unicode(val)
        if bool(args):
            self.filenames = args
        if self.action == 'list-balance' and not hasattr(self, 'account'):
            raise Exception()
            

    def usage(self):
        sys.stderr.write('''\
Usage: %(prog)s [OPTION] [FILES]...
Try `%(prog)s --help' for more information.
''' % { 'prog': APP_NAME } )
        sys.exit(1)

    def usage_extended(self):
        sys.stderr.write('''\
Usage: %(prog)s [OPTION] [FILES]...
Import OFX files into Salim database.
Example: %(prog)s -v DEBUG --import-ofx 20080801.ofx 20080701.ofx

Options:
  -v, --verbosity-level     select the verbosity level of output messages: ERROR, WARN, INFO (default), DEBUG
      --export-memos        scan the transactions stored into database and generate a file with transaction memos
                            that aren't bound to a category
      --import-csv          read a csv file and import its content into database as objects via ORM
      --import-ofx          import an ofx file into database
      --server              starts the salim http server
      --report              print a report for a given account (--account) and from a date (--date)
      --list-balance        list the balances for a given account (--account) and from a date (--date)
      --date=DATE           sets DATE to list balance
      --account=ACC         sets ACC as account when lists balance
      --help                display this help and exit
''' % { 'prog': APP_NAME } )
        sys.exit(0)
        

def check_user_environment():
    if not os.path.isdir(SALIM_HOME):
        os.mkdir(SALIM_HOME)
    salim_db = os.path.join(SALIM_HOME, 'salim.db')
    salim_db_uri = 'sqlite:%s' % salim_db
    if not os.path.isfile(salim_db):
        create_salim_database(salim_db_uri)
    else:
        create_database(salim_db_uri)


def import_ofx_files(options):
    logging.info('Given %d files: %s' % (len(options.filenames), ', '.join(options.filenames)) )
    for filename in options.filenames:
        logging.debug('Processing: %s' % filename)
        facade.import_ofx(filename)


def export_memos(options):
    memos = [stmt.memo for stmt in model.store.find(StatementTransaction, StatementTransaction.category == None)]
    logging.debug('Total memos: %d' % len(memos))
    memos = list(set(memos))
    memos.sort()
    logging.debug('Total uniq memos: %d' % len(memos))
    output_filename = 'memos.txt'
    file_memos = file(output_filename, 'w')
    for memo in memos:
        file_memos.write('%s\n' % memo.encode('utf-8'))
    file_memos.close()
    logging.info('Total %d memos saved in %s' % (len(memos), output_filename))


def import_csv(options):
    logging.info('Given %d files: %s' % (len(options.filenames), ', '.join(options.filenames)) )
    storm = StormORM(store=model.store)
    i, u, d, t = 0, 0, 0, 0
    for filename in options.filenames:
        logging.info('Processing: %s' % filename)
        file_csv = file(filename)
        results = storm.execute(file_csv, module=model)
        i += results[0]
        u += results[1]
        d += results[2]
        t += results[3]
        file_csv.close()
    logging.info('Total insert statements: %d' % i)
    logging.info('Total update statements: %d' % u)
    logging.info('Total delete statements: %d' % d)
    logging.info('Total statements: %d' % t)


def list_balance(options):
    year, month = options.date
    date = datetime.date(year, month, 1)

    transactions = [ t for t in facade.find_transactions_after_date(date, options.account) ]
    name = lambda cat: (cat and cat.name) or '-'
    for stmt, balance_amount in transactions:
        print '%10s %30.30s %30.30s %10.2f %10.2f' % (stmt.date, stmt.memo.encode('utf-8'),
            name(stmt.category), stmt.amount, balance_amount + stmt.amount)

def report(options):
    year, month = options.date
    date = datetime.date(year, month, 1)
    date_end = date + datetime.timedelta(days=30)

    transactions = [ t for t in facade.find_transactions_after_date(date, options.account) ]
    last = transactions[len(transactions)-1][0]
    balance = transactions[len(transactions)-1][1] + last.amount
    print '%-30.30s%-10s'    % ('Date:', last.date)
    print '%-30.30s%-30.30s' % ('Bank account:', last.balance.bank_account.name)
    print '-' * 60
    print '%-30.30s%10.2f'   % ('Balance:', balance)
    budget_entries_not_payed = [e for e in facade.find_budget_entries_after_date(date, date_end) if not e.payed and e.amount < 0]
    expense = reduce(lambda x,y: x + y.amount, budget_entries_not_payed, 0.0)
    print '%-30.30s%10.2f' % ('Expected expenses:', expense)
    print '-' * 40
    print '%-30.30s%10.2f' % ('Net:', balance + expense)
    print '-' * 80
    name = lambda cat: (cat and cat.name.encode('utf-8')) or '-'
    for entry in budget_entries_not_payed:
        print '%10s%30.30s%30.30s%10.2f' % ( entry.date, entry.name.encode('utf-8'), name(entry.category), entry.amount )

action_functions = {
    'import-ofx'   : import_ofx_files,
    'export-memos' : export_memos,
    'import-csv'   : import_csv,
    'list-balance' : list_balance,
    'report'       : report
}

if __name__ == '__main__':
    # reading options
    options = GetOptions()
    try:
        options.parse_options(sys.argv[1:])
    except getopt.GetoptError, e:
        options.usage()

    # setting up the user environment
    check_user_environment()
    # setting up log
    logging.basicConfig(level=options.verbosity_level,
                        format='%(asctime)s [%(levelname)s] %(message)s',
                        datefmt='%a, %d %b %Y %H:%M:%S')
    
    logging.debug('Executing action: %s' % options.action)
    action_functions[options.action](options)
